﻿/*
 * Coordinate
 * 
 * initial implementation : 29.8.2007
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Petra.Gis.Geometry;


namespace Petra.Gis.Geometry.Support
{
  /// <summary>
  /// Coordinate - general purpose point with 3d support
  /// point is readwrite
  /// </summary>
  public partial class Coordinate
  {



    public override string ToString()
    {
      return base.ToString() + "{" + this.X.ToString() + ";" + this.Y.ToString() + "}";
    }




    /// <summary>
    /// converts <c>Coordinate</c> to formated/decorated geographic coordinate string
    /// </summary>
    /// <param name="strFormat">output format string, currently supported values are "rad->ddd:mm:ss|ne" and "deg->ddd:mm:ss|ne" </param>
    /// <returns>formated string (lat, lon)</returns>
    public string ToString(string strFormat)
    {

      if (strFormat.ToLower() == "rad->ddd:mm:ss|ne")
        return num2strDMS(lat, true) + /*((lat < 0) ? "S" : "N") +*/ "N, "
           + num2strDMS(lon, true) /*+ ((lon < 0) ? "W" : "E")*/ + "E";

      if (strFormat.ToLower() == "deg->ddd:mm:ss|ne")
        return num2strDMS(lat, false) + /*((lat < 0) ? "S" : "N") +*/ "N, "
          + num2strDMS(lon, false) /*+ ((lon < 0) ? "W" : "E")*/ + "E";

      else
        return ToString();

    }





    protected string num2strDMS(double val, bool inRads)
    {
      double d = Math.Abs(val);

      if(inRads)
        d = d * 180 / Math.PI;

      double deg = Math.Floor(d);
      double min = Math.Floor((d - deg) * 60);
      double sec = Math.Round((d - deg - min / 60) * 3600, 6);

      deg *= (val < 0 ? -1 : 1);

      // add leading zeros if required
      //if (deg < 100) deg = '0' + deg;
      //if (deg < 10) deg = '0' + deg;
      //if (min < 10) min = '0' + min;
      //if (sec < 10) sec = '0' + sec;

      return /*((Math.Abs(deg) < 10) ? "00" : (Math.Abs(deg) < 100) ? "0" : "") +*/ deg.ToString()
        + '\u00B0' + ((min < 10) ? "0" : "") + min.ToString()
        + '\u2032' + ((sec < 10) ? "0" : "") + sec.ToString(CultureInfo.InvariantCulture) + '\u2033';

      //return tmp;
    }





    /// <summary>The regular expression parser used to parse the lat/long</summary>
    //private static Regex DMSParser = new Regex("^(?<deg>[-+0-9]+)[^0-9]+(?<min>[0-9]+)[^0-9]+(?<sec>[0-9.,]+)[^0-9.,ENSW]+(?<pos>[ENSW]*)$");

    private static Regex DMS = new Regex( @"^(?<latD>-?(90|\d|[0-8]\d))"           //degrees
                                        + @"[^0-9](?<latM>(\d|[0-5]\d))"             //minutes
                                        + @"[^0-9](?<latS>(\d|[0-5]\d)(\.\d+){0,1})" //seconds
                                        + @"[^0-9NS]{0,1}(?<latPos>[NS]{0,1})"
                                        + @"[^0-9]{1,3}(?<lonD>-?(180|\d|\d\d|1[0-7]\d))"     //degrees
                                        + @"[^0-9](?<lonM>(\d|[0-5]\d))"             //minutes
                                        + @"[^0-9](?<lonS>(\d|[0-5]\d)(\.\d+){0,1})" //seconds
                                        + @"[^0-9WE]{0,1}(?<lonPos>[WE]{0,1})$"
    );


    private static Regex DM = new Regex(  @"^(?<latD>-?(90|\d|[0-8]\d))"           //degrees
                                        + @"[^0-9](?<latM>(\d|[0-5]\d)(\.\d+){0,1})" //minutes
                                        + @"[^0-9NS]{0,1}(?<latPos>[NS]{0,1})"
                                        + @"[^0-9]{1,3}(?<lonD>-?(180|\d|\d\d|1[0-7]\d))"     //degrees
                                        + @"[^0-9](?<lonM>(\d|[0-5]\d)(\.\d+){0,1})" //minutes
                                        + @"[^0-9WE]{0,1}(?<lonPos>[WE]{0,1})$"
    );






    /// <summary>Parses the lat lon value.</summary>
    /// <param name="value">string value to parse</param>
    /// <remarks>It must have at least 3 parts 'degrees' 'minutes' 'seconds'. If it 
    /// has E/W and N/S this is used to change the sign.</remarks>
    /// <returns></returns>
    public static Coordinate Parse(string value)
    {
      // If it starts and finishes with a quote, strip them off
      if (value.StartsWith("\"") && value.EndsWith("\""))
        value = value.Substring(1, value.Length - 2).Replace("\"\"", "\"");

      // Now parse using the regex parser
      Match match = DMS.Match(value);
      if (!match.Success)
        throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "Lat/long value of '{0}' is not recognised", value));

      // Convert lat
      double deg = double.Parse(match.Groups["latD"].Value);
      double min = double.Parse(match.Groups["latM"].Value);
      double sec = double.Parse(match.Groups["latS"].Value, CultureInfo.InvariantCulture);
      double lat = deg + (min / 60) + (sec / 3600);
      //adjust the sign if necessary
      if (match.Groups["latPos"].Success && match.Groups["latPos"].Length > 0)
      {
        char ch = match.Groups["latPos"].Value[0];
        lat = ((ch == 'S') || (ch == 'W')) ? -lat : lat;
      }

      // Convert lon
      deg = double.Parse(match.Groups["lonD"].Value);
      min = double.Parse(match.Groups["lonM"].Value);
      sec = double.Parse(match.Groups["lonS"].Value, CultureInfo.InvariantCulture);
      double lon = deg + (min / 60) + (sec / 3600);

      //return new Coordinate(lon, lat);

      //adjust the sign if necessary
      if (match.Groups["lonPos"].Success && match.Groups["lonPos"].Length > 0)
      {
        char ch = match.Groups["lonPos"].Value[0];
        lon = ((ch == 'S') || (ch == 'W')) ? -lon : lon;
      }

      return new Coordinate(lon, lat);

    }



  }
}
